Elixirでは、プロセスはデフォルトで一時的です。関数が終了すると、プロセスも終了します。 永続的かつ状態を持つプロセス永続的に状態を保持するプロセスを作成するには、再帰を使ってループ内でプロセスを生き続けるようにします。
1. 末尾呼び出し最適化(TCO)
関数の最終的な動作が自分自身への呼び出しである場合、Erlang VM(BEAM)は 末尾呼び出し最適化を実行します。スタックに新しいフレームを追加する代わりに、単に 新しい引数とともに関数の先頭に戻ります 関数の先頭に戻り、新しい引数を用いて処理を継続します。
def factorial(n, acc), do: _fact(n-1, acc*n) # TCO
def factorial(n), do: n * factorial(n-1) # TCOではない
def factorial(n), do: n * factorial(n-1) # TCOではない
2. 永続的な状態
状態は、再帰呼び出しに更新された値を引数として渡すことで維持されます。TCOにより、これらの引数はスタック上の元のパラメータを置き換え、追加のメモリ消費なしに処理を繰り返すことが可能になり、無限ループを実現できます。
main.py
TERMINALbash — 80x24
> Ready. Click "Run" to execute.
>
QUESTION 1
What is the primary requirement for Tail-Call Optimization to occur?
The function must use the 'loop' keyword.
The recursive call must be the absolute final expression executed.
The function must be defined inside a module.
Arguments must be integers.
✅ Correct!
Correct. If any operation occurs after the call (like multiplication), the frame remains on the stack, leading to exhaustion.❌ Incorrect
TCO requires that no work remains after the recursive call so the VM can safely jump back to the start.QUESTION 2
Exercise: WorkingWithMultipleProcesses-1. Run the Spawn1 and Spawn4 code. See if you get comparable results. What is the observable difference?
Spawn1 crashes after one message; Spawn4 stays alive.
Spawn1 is faster than Spawn4.
Spawn4 uses more memory over time.
There is no observable difference.
✅ Correct!
Reference Answer: When running spawn1.exs, the process handles one message and dies. In spawn4.exs, the process recurses, remaining in the process list and capable of responding to subsequent messages.❌ Incorrect
Check the process lifecycle. One-off processes terminate immediately after their function block finishes.QUESTION 3
In pmap, we assign 'self' to 'me' before spawning. Why?
To make the code more readable.
Because 'self' inside a spawn block refers to the child process PID.
To bypass the Pin operator requirements.
✅ Correct!
Correct! If you used 'self' inside the child's block, the child would try to send the message to itself, not the parent.❌ Incorrect
Remember that 'self()' is a dynamic call; its value depends on which process is currently executing the code.QUESTION 4
Use spawn_link to start a child that sends a message and exits. If the parent sleeps for 500ms before receiving, what happens to the message?
The message is lost because the parent wasn't waiting.
The message is stored in the parent's mailbox until processed.
The parent crashes immediately.
✅ Correct!
Reference Answer: The message is placed in the mailbox. It does not matter that you weren't waiting; the BEAM buffers incoming messages. If trapping exits, you would receive both the custom message and the {:EXIT, pid, :normal} signal.❌ Incorrect
Elixir processes have mailboxes that act as buffers for incoming data.QUESTION 5
Is the order of replies in concurrent processes deterministic in theory?
Yes, they always arrive in spawn order.
No, it depends on the scheduler and execution time.
✅ Correct!
Correct. In practice, we use the Pin operator (^pid) to ensure we receive messages in a specific, deterministic order regardless of completion speed.❌ Incorrect
Concurrent execution is inherently non-deterministic. We must enforce order in our code logic.Case Study: Token Exchange Persistence
WorkingWithMultipleProcesses-2
You need to spawn two processes that receive unique tokens ('fred' and 'betty') and send them back. You must ensure the results are gathered in the correct order even if the processes finish at different times.
Q
How can you make the order of received tokens deterministic in practice?
Solution:
By capturing the PIDs of the spawned processes and using the Pin operator (^pid) in the receive block. This forces the parent to wait for the message from a specific PID before moving on to the next, regardless of which message is at the top of the mailbox.
By capturing the PIDs of the spawned processes and using the Pin operator (^pid) in the receive block. This forces the parent to wait for the message from a specific PID before moving on to the next, regardless of which message is at the top of the mailbox.
Q
Write the logical structure for a persistent 'Token' receiver using TCO.
Solution:
The receiver should be a function (e.g., 'loop') that contains a 'receive' block. After processing the token and sending it back, it must call 'loop()' as the final statement. This allows the process to handle multiple tokens over its lifetime without crashing the stack.
The receiver should be a function (e.g., 'loop') that contains a 'receive' block. After processing the token and sending it back, it must call 'loop()' as the final statement. This allows the process to handle multiple tokens over its lifetime without crashing the stack.